#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>

#define BUF_SIZE 16
#define MAX_CLOSE 4096
#define LOCKFILE "/var/run/daemonize.pid"
#define ERR_LOG_RET(msg, ...) do { syslog(LOG_ERR, msg, ##__VA_ARGS__); return -1; } while (0)

static int addlock(void);
static int daemonize(void);

int main(int argc, char *argv[])
{
  setbuf(stdout, NULL);
  openlog(NULL, LOG_CONS | LOG_PERROR | LOG_PID, LOG_USER);

  if (daemonize() == -1)
    ERR_LOG_RET("daemon demo started failed");

  syslog(LOG_INFO, "daemon demo started successfully");

  for ( ; ; )
    sleep(1);
}

static int daemonize(void)
{
  int fd, maxfd, lockfd;

  switch (fork()) {
  case -1: ERR_LOG_RET("the 1st fork() failded: %m");
  case 0: break;
  default: _exit(EXIT_SUCCESS);
  }

  if (setsid() == -1)
    ERR_LOG_RET("setsid() failed: %m");

  switch (fork()) {
  case -1: ERR_LOG_RET("the 2nd fork() failded: %m");
  case 0: break;
  default: _exit(EXIT_SUCCESS);
  }

  lockfd = addlock();
  if (lockfd == -1)
    ERR_LOG_RET("set lock to %s failed", LOCKFILE);

  umask(0);

  if (chdir("/var/log") == -1)
    ERR_LOG_RET("chdir() failed: %m");
  
  maxfd = sysconf(_SC_OPEN_MAX);
  if (maxfd == -1)
    maxfd = MAX_CLOSE;

  for (fd = 0; fd < maxfd; fd++) {
    if (fd == STDERR_FILENO || fd == lockfd)
      continue;
    if (close(fd) == -1 && errno != EBADF)
      ERR_LOG_RET("close(%d) failed: %m", fd);
  }
  
  fd = open("/dev/null", O_RDWR);
  if (fd == -1)
    ERR_LOG_RET("open(\"/dev/null\": %m");

  if (dup2(fd, STDIN_FILENO) == -1)
    ERR_LOG_RET("dup2(STDIN_FILENO) failed: %m");

  if (dup2(fd, STDOUT_FILENO) == -1)
    ERR_LOG_RET("dup2(STDOUT_FILENO) failed: %m");

#if 0
  if (dup2(fd, STDERR_FILENO) == -1)
    ERR_LOG_RET("dup2(STDERR_FILENO) failed: %m");
#endif
  
  return 0;
}

static int addlock(void)
{
  int lockfd, flags;
  struct flock fl_w;
  char buf[BUF_SIZE];

  lockfd = open(LOCKFILE, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
  if (lockfd == -1)
    ERR_LOG_RET("open() lockfd failed: %m");

  flags = fcntl(lockfd, F_GETFD);
  if (flags == -1)
    ERR_LOG_RET("fcntl() to get flags failed: %m");

  flags |= FD_CLOEXEC;
  if (fcntl(lockfd, F_SETFD, flags) == -1)
    ERR_LOG_RET("fcntl() to set flags failed: %m");

  fl_w.l_type = F_WRLCK;
  fl_w.l_whence = SEEK_SET;
  fl_w.l_start = 0;
  fl_w.l_len = 0;

  if (fcntl(lockfd, F_SETLK, &fl_w) == -1)
    ERR_LOG_RET("fcntl() to set lock failed: %m");
  
  if (ftruncate(lockfd, 0) == -1)
    ERR_LOG_RET("ftruncate() LOCKFILE failed: %m");
  
  snprintf(buf, BUF_SIZE, "%ld\n", (long)getpid());
  if (write(lockfd, buf, strlen(buf)) != strlen(buf))
    ERR_LOG_RET("write() fd to LOCKFILE failed: %m");
  
  return lockfd;
}
